namespace ActiveRecord

import System
import System.Collections.Generic
import System.Data
import System.Reflection
import System.Text
import System.Text.RegularExpressions
import Inflector.Net

partial class ActiveRecordBase:
   
   private static def load(activeRecord as ActiveRecordBase):
      load(activeRecord, string.Empty)

   # this method has logic to decide when to reload self and associations
   private static def load(activeRecord as ActiveRecordBase, associatedTableName as string):
      debug "load(${activeRecord.TableName}, ${associatedTableName}) "#_taintedPK: ${activeRecord._taintedPK}, _taintedFKs.Count: ${activeRecord._taintedFKs.Count}, _taintedContent: ${activeRecord._taintedContent}. -- ${activeRecord}"
      
      /*
       * build a join query which includes all eager tainted foreign keys
       * fetch the data
       * create table records from the data (have to convert fk names to pk names)
       * and save in associations / columns
       */
      
      // build a join query for all tainted fks
      joinSql = StringBuilder("select {0} from ${activeRecord.TableName} ")
      joinTables = List of string()
      joinAssns = List of AssociationAttribute()
      columns = List of string()
      
      # add columns for this activeRecord
      columns.Add(string.Join(", ",  array("${activeRecord.TableName}.${col} as ${activeRecord.TableName}_${col}" for col in _activeRecordTypeColumns[activeRecord.GetType().AssemblyQualifiedName])))
      
      if activeRecord._taintedPK: # re-load self load all eager associations
         for attList as Dictionary [of string, AssociationAttribute] in (activeRecord.HasOne, activeRecord.HasMany, activeRecord.HasAndBelongsToMany, activeRecord.BelongsTo):
            for table as string in attList.Keys:
               assn = attList[table]
               if not assn.Lazy:
                  joinTables.Add(table)
                  joinAssns.Add(assn)
                  fkName = activeRecord.getFKName(table, assn)
                  joinSql.Append(getJoinSql(table, assn, fkName, activeRecord, columns))
                  
      elif activeRecord._taintedFKs.Count > 0: # re-load all eager associations in list
         for table as string in activeRecord._taintedFKs.Keys:
            #raise NotImplementedException("have to figure out how to reload tainted fks")
            fkName = activeRecord._taintedFKs[table]
            assn = activeRecord._taintedFKAssns[fkName]
            if not assn.Lazy:
               joinTables.Add(table)
               joinAssns.Add(assn)
               joinSql.Append(getJoinSql(table, assn, fkName, activeRecord, columns))
         activeRecord._taintedFKs.Clear()
         activeRecord._taintedFKAssns.Clear()
      
      
      # if associatedTableName was given, add a join to it
      if not Utils.IsEmpty( associatedTableName ) and not joinTables.Contains(associatedTableName):
         assn = activeRecord.hasAssociation(associatedTableName)
         joinTables.Add( associatedTableName )
         joinAssns.Add( assn )
         fkName = activeRecord.getFKName( associatedTableName, assn )
         joinSql.Append(getJoinSql(associatedTableName, assn, fkName, activeRecord, columns))
      
      # set where clause, if primary key field present, use primary key
      arWhere as WhereExpression
      if activeRecord.HasValueAtColumn(activeRecord.PrimaryKey):
         arWhere = SQL.Where("${activeRecord.TableName}.${activeRecord.PrimaryKey}", activeRecord[activeRecord.PrimaryKey])
      else: # otherwise, use @@identity
         arWhere = SQL.Where("${activeRecord.TableName}.${activeRecord.PrimaryKey}", "@@identity")
      
      joinSql.Append(" where ${arWhere}")
      
      colStr = string.Join(", ", array(string, columns))
      joinData = ActiveRecordBase.FindBySql(string.Format(joinSql.ToString(),  colStr))
      #debug "joinData returned ${joinData.Length} rows"
      if joinTables.Count == 0:
         joinTables.Add(activeRecord.TableName)
         joinAssns.Add(null)
      for i as int, table as string in enumerate(joinTables):
         tbl = ActiveTable(table)
         typeName = getAssociatedTypeNameFromTable(table, joinAssns[i])
         ar = getTypeInstance(typeName)
         #debug "working with join table ${table}, typeName ${typeName}"
         for i as int, row as ActiveRecordBase in enumerate(joinData):
            #debug "  working with row ${row}"
            arNewRow = Activator.CreateInstance(ar.GetType()) as ActiveRecordBase
            for column in row.Columns:
               joinTablePrefix = "${table}_"
               #debug "    joinTablePrefix: ${joinTablePrefix}, working with column ${column}, value ${row[column]}"
               regJoinTbl = Regex("^${joinTablePrefix}")
               
               if activeRecord.TableName != table and regJoinTbl.IsMatch(column): 
                  colName = regJoinTbl.Replace(column, string.Empty)
                  arNewRow[colName] = row[column]
                  debug "      set ${table}.${colName} value ${arNewRow[colName]}"
               
               if i == 0:
                  selfTableName = "${activeRecord.TableName}_"
                  regSelfTbl = Regex("^${selfTableName}")
                  if regSelfTbl.IsMatch(column):
                     #debug "idxSelfTable: ${idxSelfTable}"
                     colName = regSelfTbl.Replace(column, string.Empty)
                     #debug "      selfTableName: ${selfTableName}, setting column ${table}.${colName}, value ${row[column]}"
                     activeRecord[colName] = row[column]
            
            if arNewRow.Columns.Length > 0:
               #debug "  adding row to table"
               tbl.Add(arNewRow)
            #else:
               #debug "  !!didn't add row to table!!"
         
         if activeRecord.TableName != table:
            if activeRecord._associations.ContainsKey(table):
               activeRecord._associations[table] = tbl
            else:
               activeRecord._associations.Add(table, tbl)
      
      activeRecord._taintedPK = false

   # this method just loads the active record with no logic 
   private static def loadActiveRecord(activeRecord as ActiveRecordBase):
      where as WhereExpression

      # if primary key field not null, do update
      if activeRecord.HasValueAtColumn(activeRecord.PrimaryKey):
         where = SQL.Where(activeRecord.PrimaryKey, activeRecord[activeRecord.PrimaryKey])
      else: # primary key field null, assume that insert just happened and use @@identity as pk value
         where = SQL.Where(activeRecord.PrimaryKey, "@@identity")
      
      if activeRecord.usesInheritance():
         tableName = getTypeInstance(getInheritanceBaseType(activeRecord.GetType())).TableName
         tbl = ActiveRecordBase.FindBySql(getSelectCmdFromConditions(tableName , where), activeRecord.GetType())
      else:
         tbl = ActiveRecordBase.FindBySql(getSelectCmdFromConditions(activeRecord, where), activeRecord.GetType())
      activeRecord._columns = tbl[0]._columns
      
   private static def registerActiveRecord(rec as ActiveRecordBase):
      # don't add when called by ActiveRecordBase, just for derived types because 
      # Activator.CreateInstance(ActiveRecordBase) gets called and typeName != "ActiveRecord.ActiveRecordBase"
      recType = rec.GetType() as Type
      typeName = recType.AssemblyQualifiedName
      if not _activeRecordTableNames.ContainsKey(typeName) and recType != typeof(ActiveRecordBase):
         #debug "registerActiveRecord(${recType})"
         baseType = recType.BaseType
         baseTypeName = baseType.AssemblyQualifiedName
         
         # register base types frist
         if not _activeRecordTableNames.ContainsKey(baseTypeName) and baseType != typeof(ActiveRecordBase):
            Activator.CreateInstance(baseType)
         
         # get the table name here
         # tableName - if not provided, pluralize lowercase type name
         activeAttrs = recType.GetCustomAttributes(typeof(ActiveRecordAttribute), true)
         if not Utils.IsEmpty(activeAttrs):
            _activeRecordAttribute = activeAttrs[0] as ActiveRecordAttribute
            
         if not _activeRecordAttribute is null:
            if not Utils.IsEmpty(_activeRecordAttribute.TableName):
               rec.TableName = _activeRecordAttribute.TableName
         
         if Utils.IsEmpty(rec.TableName):
            rec.TableName = Inflector.Pluralize( recType.Name.ToLower() )
         
         # get inheritance column, save 
         if not _activeRecordAttribute == null and not Utils.IsEmpty(_activeRecordAttribute.InheritanceColumn):
            _activeRecordInheritanceColumns.Add(typeName, _activeRecordAttribute.InheritanceColumn)
         elif not _activeRecordInheritanceColumns.ContainsKey(typeName):
            _activeRecordInheritanceColumns.Add(typeName, DEFAULT_INHERITANCE_KEY)
         
         #debug "${recType.FullName} - added inheritance column ${_activeRecordInheritanceColumns[typeName]}"

         # find all the columns from this table and add to _activeRecordTypeColumns
         # also, store all columns for this table
         
         tblCols as ActiveTable
         
         # if this type isn't a direct decendant of ActiveRecordBase, use the base inheritance type
         if recType.BaseType.IsSubclassOf(typeof(ActiveRecordBase)):
            inheritanceBaseType = getInheritanceBaseType(recType.BaseType)
            if inheritanceBaseType != typeof(ActiveRecordBase):
               baseClassInstance = getTypeInstance(inheritanceBaseType.AssemblyQualifiedName)
               #debug "type: ${recType.FullName}, baseTypeName: ${recType.BaseType.FullName}, inheritance base type: ${baseClassInstance.GetType().FullName}."
               if baseClassInstance.usesInheritance():
                  tblCols = ActiveRecordBase.FindBySql("show columns from ${baseClassInstance.TableName}")
                  rec.TableName = baseClassInstance.TableName
                  goto usedBaseTable
         
         tblCols = ActiveRecordBase.FindBySql("show columns from ${rec.TableName}")
         :usedBaseTable
         
         # now ok to reference TableName
         #debug "${rec.GetType().FullName} - TableName: ${rec.TableName}"
         #debug "registerActiveRecord(${rec.TableName}) - typeName: ${typeName}.  _activeRecordTableNames.Keys: ${join(_activeRecordTableNames.Keys, ',')}"
         #debug "registerActiveRecord(${rec.TableName}) - typeName: ${typeName}"
         
         cols = array(string, tblCols.Length)
         #debug "cols.Length: ${tblCols.Length}"
         for i as int, ar as ActiveRecordBase in enumerate(tblCols):
            cols[i] = ar["Field"]
         _activeRecordTypeColumns.Add(typeName, cols)
         #debug "registerActiveRecord(${rec.TableName})  columns are: ${join(cols, ',')}"
         
         # associations
         #debug "begin get associations"
         _aryBelongsTo = AttrList.GetAssociationAttributes of BelongsToAttribute (recType)
         _aryHasOne = AttrList.GetAssociationAttributes of HasOneAttribute (recType) 
         _aryHasMany = AttrList.GetAssociationAttributes of HasManyAttribute (recType)
         _aryHasAndBelongsToMany = AttrList.GetAssociationAttributes of HasAndBelongsToManyAttribute (recType)
         #debug "end get associations"
         
         aras = ActiveRecordAttributeSet()
         aras.ActiveRecordAttribute = _activeRecordAttribute
         aras.BelongsToAttributes = _aryBelongsTo
         aras.HasOneAttributes = _aryHasOne
         aras.HasManyAttributes = _aryHasMany
         aras.HasAndBelongsToManyAttributes = _aryHasAndBelongsToMany
         
         #debug "adding ActiveRecordAttributeSet for ${recType}.  ${aras}"
         _activeRecordAttributes.Add(typeName, aras)
         _activeRecordTypes.Add(typeName, rec)
         _activeRecordClasses.Add(typeName)
         _activeRecordTables.Add(rec.TableName)
         
         #debug "adding base type: ${baseTypeName} for type ${typeName}"
         _activeRecordBaseTypes.Add(typeName, baseTypeName)
         _activeRecordTypeNamesShort.Add(recType.FullName, typeName)
         
         

   // instantiate all types derived from ActiveRecordBase which haven't been registered yet
   private static def staticInit():
      if Utils.IsEmpty(_connectionProvider) or Utils.IsEmpty(_connectionString):
         return
      assemblies = AppDomain.CurrentDomain.GetAssemblies()
      for assembly as Assembly in assemblies:
         if assembly.FullName.StartsWith("System") or assembly.FullName.StartsWith("Boo.") or assembly.FullName.StartsWith("mscorlib"):
            continue
         #debug "staticInit() Assembly: ${assembly.FullName}"
         for type as Type in assembly.GetTypes():
            #debug " checking ${type}"
            if _activeRecordTypes.ContainsKey(type.AssemblyQualifiedName):
               continue
            
            if type.IsSubclassOf( typeof(ActiveRecordBase) ):
               #debug "  ${type}.IsSubclassOf(ActiveRecordBase) returned TRUE"
               Activator.CreateInstance(type)
            #else:
            #   debug "  ${type}.IsSubclassOf(ActiveRecordBase) returned FALSE"
         
   private static def stringAryToHash(*attrs as (string)) as Hash:
      h = {}
      i = 0
      #debug "attributes were: " + join(attrs, ',')
      while i + 1 < attrs.Length:
         h[attrs[i]] = attrs[++i]
         ++i
      return h
   
   private static def updateCounter(tableName as string, counterName as string, id, primaryKey as string, count as int) as int:
      counterSql as string
      if count > 0:
         counterSql = "${counterName} = ${counterName} + ${count}"
      elif count < 0:
         count = -count
         counterSql = "${counterName} = ${counterName} - ${count}"
         
      if count != 0:
         return NonQuery(getUpdateCmd(tableName, counterSql, SQL.Where(primaryKey, id)))
      else: 
         return 0
   
   private static def usesInheritance(type as Type) as bool:
      return usesInheritance(type.AssemblyQualifiedName)
   
   private static def usesInheritance(typeName as string) as bool:
      #debug "usesInheritance(${typeName})"
      if _activeRecordTypeColumns.ContainsKey(typeName):
            inheritColumn = _activeRecordInheritanceColumns[typeName]
            #debug "  inheritColumn: ${inheritColumn}, columns: ${join(_activeRecordTypeColumns[typeName], ',')}"
            if Array.IndexOf(_activeRecordTypeColumns[typeName], inheritColumn) != -1:
               return true
      
      return false
      
   private static def verifyConnectParams():
      if Utils.IsEmpty(_connectionProvider) or Utils.IsEmpty(_connectionString):
         raise "verifyConnectParams(): Must set ActiveRecordBase.ConnectionProvider and ActiveRecordBase.ConnectionString before using this method"
   